/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package com.vii.streamline.services.reflection;

import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.structures.collections.InnerList;
import org.slf4j.Logger;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Reflection Manager type dealing with the execution of Invication objects
 */
public class ReflectionInvocationManager implements Invocable {

    protected Logger                                    logger;

    protected Map<String, ReflectionReference>          references;
    protected Map<String, Method>                       reflectReferences;
    protected Map<String, List<String>>                 reflectFormalParameterNames;
    protected Map<String, List<Class>>                  reflectFormalParameterTypes;
    protected Map<String, Map<String,Class>>            reflectFormalParameterMaps;

    {
        reflectReferences           = new HashMap<String, Method>();
        reflectFormalParameterNames = new HashMap<String, List<String>>();
        reflectFormalParameterTypes = new HashMap<String, List<Class>>();
        reflectFormalParameterMaps  = new HashMap<String, Map<String,Class>>();
    }

    public ReflectionInvocationManager(Logger logger, Map<String, ReflectionReference> references) throws StreamLineException {
        this.logger         = logger;
        this.references     = references;

        collectReflectionReference();
    }

    protected String getMethodName(String referenceName){
        return  referenceName.contains( Invocation.REFERENCE_SEPARATOR )
                ? referenceName.substring( referenceName.indexOf( Invocation.REFERENCE_SEPARATOR ) + 1 )
                : referenceName;
    }

    protected void collectReflectionReference() throws StreamLineException {
        for( String referenceName : references.keySet() ){
            ReflectionReference reference = references.get( referenceName );

            try {
                reflectReferences.put(
                        referenceName,
                        ReflectionServices.getMethod(reference.getTypeReference(), getMethodName( referenceName ) )
                );
                reflectFormalParameterNames.put(
                        referenceName,
                        ReflectionServices.getFormalParameterNames(reflectReferences.get( referenceName ) )
                );
                reflectFormalParameterTypes.put(
                        referenceName,
                        ReflectionServices.getFormalParameterTypes( reflectReferences.get(referenceName) )
                );
                reflectFormalParameterMaps.put(
                        referenceName,
                        ReflectionServices.getFormalParameterMap(reflectReferences.get( referenceName ) )
                );
            } catch (NoSuchMethodException e) {
                throw new StreamLineException("Cannot find the referred member: " + reference );
            }
        }
    }

    public String getMethodReferenceName(Invocation invocation) {
        for( String s : references.keySet() ){
            if(
                ( s.contains( Invocation.REFERENCE_SEPARATOR ) && s.equalsIgnoreCase( invocation.getEntity() + Invocation.REFERENCE_SEPARATOR + invocation.getMethod() ) ) ||
                s.equalsIgnoreCase( invocation.getMethod() )
            )
                return s;
        }
        return null;
    }

    @Override
    public boolean isMethodReckognised(Invocation invocation) {
        return getMethodReferenceName( invocation ) != null;
    }

    @Override
    public boolean hasMessageProcessorReturnValue(Invocation invocation ) throws StreamLineException{
        if( !isMethodReckognised(invocation) )
            throw new StreamLineException("Invocation's target cannot be identified.");

        return !reflectReferences.get( getMethodReferenceName(invocation) ).getReturnType( ).equals( Void.class );
    }

    @Override
    public List<String> getParameterNames(Invocation invocation) throws StreamLineException {
        if( !isMethodReckognised(invocation) )
            throw new StreamLineException("Invocation's target cannot be identified.");

        return reflectFormalParameterNames.get( getMethodReferenceName(invocation) );
    }

    @Override
    public List<Class> getParameterTypes(Invocation invocation) throws StreamLineException {
        if( !isMethodReckognised(invocation) )
            throw new StreamLineException("Invocation's target cannot be identified.");

        return reflectFormalParameterTypes.get( getMethodReferenceName(invocation) );
    }

    public Map<String, Object> getParameterMap(Invocation invocation, InnerList<Object> parameters) throws StreamLineException {
        List<String> parameterNames = getParameterNames( invocation );

        Map<String, Object> res = new HashMap<String, Object>();

        for( int i=0; i<parameterNames.size(); ++i ){
            String parameterName = parameterNames.get( i );
            res.put( parameterName, parameters.get(i) );
        }

        return res;
    }

    @Override
    public Map<String, Class> getParameterMap(Invocation invocation) throws StreamLineException {
        if( !isMethodReckognised(invocation) )
            throw new StreamLineException("Invocation's target cannot be identified.");

        return reflectFormalParameterMaps.get( getMethodReferenceName(invocation) );
    }

    public Object[] getParameters(Invocation invocation) throws StreamLineException {
        List<String> formalParameterNames = getParameterNames( invocation );

        Object[] params = new Object[ formalParameterNames.size() ];
            for( int i=0; i<formalParameterNames.size(); ++i )
                params[i] = invocation.getParameters().get( formalParameterNames.get(i) );
        return params;
    }

    @Override
    public Object execute(Invocation invocation) throws StreamLineException {
        try {
            String referenceName = getMethodReferenceName( invocation );

            ReflectionReference ref = references.get( referenceName );

            logger.debug(
                "Calling " + ref + " by: " + invocation
            );

            boolean hasReturnValue = hasMessageProcessorReturnValue( invocation );

            List<String> formalParameterNames = getParameterNames( invocation );

            Object[] params = getParameters( invocation );

            Object o = ReflectionServices.invokeMethodALike(
                    reflectReferences.get( referenceName ),
                    ref.isStaticReference() ? ref.getTypeReference() : ref.newInstance(),
                    params
            );
            return hasReturnValue ? o : "Success";
    	}catch(Exception e) {
            e.printStackTrace();
            throw new StreamLineException( e.getMessage() );
        }
    }

    @Override
    public String toString() {
        return "ReflectionInvocationManager{" +
                "logger=" + logger +
                ", references=" + references +
                ", reflectReferences=" + reflectReferences +
                ", reflectFormalParameterNames=" + reflectFormalParameterNames +
                ", reflectFormalParameterTypes=" + reflectFormalParameterTypes +
                ", reflectFormalParameterMaps=" + reflectFormalParameterMaps +
                '}';
    }

}
